/** @file
The module to produce Usb Bus PPI.

Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>

SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "UsbPeim.h"
#include "HubPeim.h"
#include "PeiUsbLib.h"

//
// UsbIo PPI interface function
//
PEI_USB_IO_PPI         mUsbIoPpi = {
  PeiUsbControlTransfer,
  PeiUsbBulkTransfer,
  PeiUsbGetInterfaceDescriptor,
  PeiUsbGetEndpointDescriptor,
  PeiUsbPortReset
};

EFI_PEI_PPI_DESCRIPTOR mUsbIoPpiList = {
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gPeiUsbIoPpiGuid,
  NULL
};

/**
  The enumeration routine to detect device change.

  @param  PeiServices            Describes the list of possible PEI Services.
  @param  UsbHcPpi               The pointer of PEI_USB_HOST_CONTROLLER_PPI instance.
  @param  Usb2HcPpi              The pointer of PEI_USB2_HOST_CONTROLLER_PPI instance.

  @retval EFI_SUCCESS            The usb is enumerated successfully.
  @retval EFI_OUT_OF_RESOURCES   Can't allocate memory resource.
  @retval Others                 Other failure occurs.

**/
EFI_STATUS
PeiUsbEnumeration (
  IN EFI_PEI_SERVICES               **PeiServices,
  IN PEI_USB_HOST_CONTROLLER_PPI    *UsbHcPpi,
  IN PEI_USB2_HOST_CONTROLLER_PPI    *Usb2HcPpi
  );

/**
  Configure new detected usb device.

  @param  PeiServices            Describes the list of possible PEI Services.
  @param  PeiUsbDevice           The pointer of PEI_USB_DEVICE instance.
  @param  Port                   The port to be configured.
  @param  DeviceAddress          The device address to be configured.

  @retval EFI_SUCCESS            The new detected usb device is configured successfully.
  @retval EFI_OUT_OF_RESOURCES   Can't allocate memory resource.
  @retval Others                 Other failure occurs.

**/
EFI_STATUS
PeiConfigureUsbDevice (
  IN     EFI_PEI_SERVICES    **PeiServices,
  IN     PEI_USB_DEVICE      *PeiUsbDevice,
  IN     UINT8               Port,
  IN OUT UINT8               *DeviceAddress
  );

/**
  Get all configurations from a detected usb device.

  @param  PeiServices            Describes the list of possible PEI Services.
  @param  PeiUsbDevice           The pointer of PEI_USB_DEVICE instance.

  @retval EFI_SUCCESS            The new detected usb device is configured successfully.
  @retval EFI_OUT_OF_RESOURCES   Can't allocate memory resource.
  @retval Others                 Other failure occurs.

**/
EFI_STATUS
PeiUsbGetAllConfiguration (
  IN EFI_PEI_SERVICES   **PeiServices,
  IN PEI_USB_DEVICE     *PeiUsbDevice
  );

/**
  Get the start position of next wanted descriptor.

  @param  Buffer            Buffer containing data to parse.
  @param  Length            Buffer length.
  @param  DescType          Descriptor type.
  @param  DescLength        Descriptor length.
  @param  ParsedBytes       Bytes has been parsed.

  @retval EFI_SUCCESS       Get wanted descriptor successfully.
  @retval EFI_DEVICE_ERROR  Error occurred.

**/
EFI_STATUS
GetExpectedDescriptor (
  IN  UINT8       *Buffer,
  IN  UINTN       Length,
  IN  UINT8       DescType,
  IN  UINT8       DescLength,
  OUT UINTN       *ParsedBytes
  );

/**
  The entrypoint of the module, it will enumerate all HCs.

  @param  FileHandle             Handle of the file being invoked.
  @param  PeiServices            Describes the list of possible PEI Services.

  @retval EFI_SUCCESS            Usb initialization is done successfully.
  @retval EFI_OUT_OF_RESOURCES   Can't allocate memory resource.
  @retval EFI_UNSUPPORTED        Can't find required PPI.

**/
EFI_STATUS
EFIAPI
PeimInitializeUsb (
  IN EFI_PEI_FILE_HANDLE        FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
{
  EFI_STATUS                   Status;
  UINTN                        Index;
  PEI_USB_HOST_CONTROLLER_PPI  *UsbHcPpi;
  PEI_USB2_HOST_CONTROLLER_PPI *Usb2HcPpi;

  if (!EFI_ERROR (PeiServicesRegisterForShadow (FileHandle))) {
    return EFI_SUCCESS;
  }

  //
  // gPeiUsbHostControllerPpiGuid and gPeiUsb2HostControllerPpiGuid should not
  // be produced at the same time
  //
  Index = 0;
  while (TRUE) {
    //
    // Get UsbHcPpi at first.
    //
    Status = PeiServicesLocatePpi (
               &gPeiUsbHostControllerPpiGuid,
               Index,
               NULL,
               (VOID **) &UsbHcPpi
               );
    if (EFI_ERROR (Status)) {
      //
      // No more host controller, break out
      //
      break;
    }
    PeiUsbEnumeration ((EFI_PEI_SERVICES **) PeiServices, UsbHcPpi, NULL);
    Index++;
  }

  if (Index == 0) {
    //
    // Then try to get Usb2HcPpi.
    //
    while (TRUE) {
      Status = PeiServicesLocatePpi (
                 &gPeiUsb2HostControllerPpiGuid,
                 Index,
                 NULL,
                 (VOID **) &Usb2HcPpi
                 );
      if (EFI_ERROR (Status)) {
        //
        // No more host controller, break out
        //
        break;
      }
      PeiUsbEnumeration ((EFI_PEI_SERVICES **) PeiServices, NULL, Usb2HcPpi);
      Index++;
    }
  }

  if (Index == 0) {
    return EFI_UNSUPPORTED;
  }

  return EFI_SUCCESS;
}

/**
  The Hub Enumeration just scans the hub ports one time. It also
  doesn't support hot-plug.

  @param  PeiServices            Describes the list of possible PEI Services.
  @param  PeiUsbDevice           The pointer of PEI_USB_DEVICE instance.
  @param  CurrentAddress         The DeviceAddress of usb device.

  @retval EFI_SUCCESS            The usb hub is enumerated successfully.
  @retval EFI_OUT_OF_RESOURCES   Can't allocate memory resource.
  @retval Others                 Other failure occurs.

**/
EFI_STATUS
PeiHubEnumeration (
  IN EFI_PEI_SERVICES               **PeiServices,
  IN PEI_USB_DEVICE                 *PeiUsbDevice,
  IN UINT8                          *CurrentAddress
  )
{
  UINTN                 Index;
  EFI_STATUS            Status;
  PEI_USB_IO_PPI        *UsbIoPpi;
  EFI_USB_PORT_STATUS   PortStatus;
  UINTN                 MemPages;
  EFI_PHYSICAL_ADDRESS  AllocateAddress;
  PEI_USB_DEVICE        *NewPeiUsbDevice;
  UINTN                 InterfaceIndex;
  UINTN                 EndpointIndex;


  UsbIoPpi    = &PeiUsbDevice->UsbIoPpi;

  DEBUG ((EFI_D_INFO, "PeiHubEnumeration: DownStreamPortNo: %x\n", PeiUsbDevice->DownStreamPortNo));

  for (Index = 0; Index < PeiUsbDevice->DownStreamPortNo; Index++) {

    Status = PeiHubGetPortStatus (
              PeiServices,
              UsbIoPpi,
              (UINT8) (Index + 1),
              (UINT32 *) &PortStatus
              );

    if (EFI_ERROR (Status)) {
      continue;
    }

    DEBUG ((EFI_D_INFO, "USB Status --- Port: %x ConnectChange[%04x] Status[%04x]\n", Index, PortStatus.PortChangeStatus, PortStatus.PortStatus));
    //
    // Only handle connection/enable/overcurrent/reset change.
    //
    if ((PortStatus.PortChangeStatus & (USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE | USB_PORT_STAT_C_OVERCURRENT | USB_PORT_STAT_C_RESET)) == 0) {
      continue;
    } else {
      if (IsPortConnect (PortStatus.PortStatus)) {
        //
        // Begin to deal with the new device
        //
        MemPages = sizeof (PEI_USB_DEVICE) / EFI_PAGE_SIZE + 1;
        Status = PeiServicesAllocatePages (
                   EfiBootServicesCode,
                   MemPages,
                   &AllocateAddress
                   );
        if (EFI_ERROR (Status)) {
          return EFI_OUT_OF_RESOURCES;
        }

        NewPeiUsbDevice = (PEI_USB_DEVICE *) ((UINTN) AllocateAddress);
        ZeroMem (NewPeiUsbDevice, sizeof (PEI_USB_DEVICE));

        NewPeiUsbDevice->Signature        = PEI_USB_DEVICE_SIGNATURE;
        NewPeiUsbDevice->DeviceAddress    = 0;
        NewPeiUsbDevice->MaxPacketSize0   = 8;
        NewPeiUsbDevice->DataToggle       = 0;
        CopyMem (
          &(NewPeiUsbDevice->UsbIoPpi),
          &mUsbIoPpi,
          sizeof (PEI_USB_IO_PPI)
          );
        CopyMem (
          &(NewPeiUsbDevice->UsbIoPpiList),
          &mUsbIoPpiList,
          sizeof (EFI_PEI_PPI_DESCRIPTOR)
          );
        NewPeiUsbDevice->UsbIoPpiList.Ppi = &NewPeiUsbDevice->UsbIoPpi;
        NewPeiUsbDevice->AllocateAddress  = (UINTN) AllocateAddress;
        NewPeiUsbDevice->UsbHcPpi         = PeiUsbDevice->UsbHcPpi;
        NewPeiUsbDevice->Usb2HcPpi        = PeiUsbDevice->Usb2HcPpi;
        NewPeiUsbDevice->Tier             = (UINT8) (PeiUsbDevice->Tier + 1);
        NewPeiUsbDevice->IsHub            = 0x0;
        NewPeiUsbDevice->DownStreamPortNo = 0x0;

        if (((PortStatus.PortChangeStatus & USB_PORT_STAT_C_RESET) == 0) ||
             ((PortStatus.PortStatus & (USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE)) == 0)) {
          //
          // If the port already has reset change flag and is connected and enabled, skip the port reset logic.
          //
          PeiResetHubPort (PeiServices, UsbIoPpi, (UINT8)(Index + 1));

          PeiHubGetPortStatus (
             PeiServices,
             UsbIoPpi,
             (UINT8) (Index + 1),
             (UINT32 *) &PortStatus
             );
        } else {
          PeiHubClearPortFeature (
            PeiServices,
            UsbIoPpi,
            (UINT8) (Index + 1),
            EfiUsbPortResetChange
            );
        }

        NewPeiUsbDevice->DeviceSpeed = (UINT8) PeiUsbGetDeviceSpeed (PortStatus.PortStatus);
        DEBUG ((EFI_D_INFO, "Device Speed =%d\n", PeiUsbDevice->DeviceSpeed));

        if (USB_BIT_IS_SET (PortStatus.PortStatus, USB_PORT_STAT_SUPER_SPEED)){
          NewPeiUsbDevice->MaxPacketSize0 = 512;
        } else if (USB_BIT_IS_SET (PortStatus.PortStatus, USB_PORT_STAT_HIGH_SPEED)) {
          NewPeiUsbDevice->MaxPacketSize0 = 64;
        } else if (USB_BIT_IS_SET (PortStatus.PortStatus, USB_PORT_STAT_LOW_SPEED)) {
          NewPeiUsbDevice->MaxPacketSize0 = 8;
        } else {
          NewPeiUsbDevice->MaxPacketSize0 = 8;
        }

        if(NewPeiUsbDevice->DeviceSpeed != EFI_USB_SPEED_HIGH) {
          if (PeiUsbDevice->DeviceSpeed == EFI_USB_SPEED_HIGH) {
            NewPeiUsbDevice->Translator.TranslatorPortNumber = (UINT8)Index;
            NewPeiUsbDevice->Translator.TranslatorHubAddress = *CurrentAddress;
          } else {
            CopyMem(&(NewPeiUsbDevice->Translator), &(PeiUsbDevice->Translator), sizeof(EFI_USB2_HC_TRANSACTION_TRANSLATOR));
          }
        }

        //
        // Configure that Usb Device
        //
        Status = PeiConfigureUsbDevice (
                  PeiServices,
                  NewPeiUsbDevice,
                  (UINT8) (Index + 1),
                  CurrentAddress
                  );

        if (EFI_ERROR (Status)) {
          continue;
        }
        DEBUG ((EFI_D_INFO, "PeiHubEnumeration: PeiConfigureUsbDevice Success\n"));

        Status = PeiServicesInstallPpi (&NewPeiUsbDevice->UsbIoPpiList);

        if (NewPeiUsbDevice->InterfaceDesc->InterfaceClass == 0x09) {
          NewPeiUsbDevice->IsHub  = 0x1;

          Status = PeiDoHubConfig (PeiServices, NewPeiUsbDevice);
          if (EFI_ERROR (Status)) {
            return Status;
          }

          PeiHubEnumeration (PeiServices, NewPeiUsbDevice, CurrentAddress);
        }

        for (InterfaceIndex = 1; InterfaceIndex < NewPeiUsbDevice->ConfigDesc->NumInterfaces; InterfaceIndex++) {
          //
          // Begin to deal with the new device
          //
          MemPages = sizeof (PEI_USB_DEVICE) / EFI_PAGE_SIZE + 1;
          Status = PeiServicesAllocatePages (
                     EfiBootServicesCode,
                     MemPages,
                     &AllocateAddress
                     );
          if (EFI_ERROR (Status)) {
            return EFI_OUT_OF_RESOURCES;
          }
          CopyMem ((VOID *)(UINTN)AllocateAddress, NewPeiUsbDevice, sizeof (PEI_USB_DEVICE));
          NewPeiUsbDevice = (PEI_USB_DEVICE *) ((UINTN) AllocateAddress);
          NewPeiUsbDevice->AllocateAddress  = (UINTN) AllocateAddress;
          NewPeiUsbDevice->UsbIoPpiList.Ppi = &NewPeiUsbDevice->UsbIoPpi;
          NewPeiUsbDevice->InterfaceDesc = NewPeiUsbDevice->InterfaceDescList[InterfaceIndex];
          for (EndpointIndex = 0; EndpointIndex < NewPeiUsbDevice->InterfaceDesc->NumEndpoints; EndpointIndex++) {
            NewPeiUsbDevice->EndpointDesc[EndpointIndex] = NewPeiUsbDevice->EndpointDescList[InterfaceIndex][EndpointIndex];
          }

          Status = PeiServicesInstallPpi (&NewPeiUsbDevice->UsbIoPpiList);

          if (NewPeiUsbDevice->InterfaceDesc->InterfaceClass == 0x09) {
            NewPeiUsbDevice->IsHub  = 0x1;

            Status = PeiDoHubConfig (PeiServices, NewPeiUsbDevice);
            if (EFI_ERROR (Status)) {
              return Status;
            }

            PeiHubEnumeration (PeiServices, NewPeiUsbDevice, CurrentAddress);
          }
        }
      }
    }
  }


  return EFI_SUCCESS;
}

/**
  The enumeration routine to detect device change.

  @param  PeiServices            Describes the list of possible PEI Services.
  @param  UsbHcPpi               The pointer of PEI_USB_HOST_CONTROLLER_PPI instance.
  @param  Usb2HcPpi              The pointer of PEI_USB2_HOST_CONTROLLER_PPI instance.

  @retval EFI_SUCCESS            The usb is enumerated successfully.
  @retval EFI_OUT_OF_RESOURCES   Can't allocate memory resource.
  @retval Others                 Other failure occurs.

**/
EFI_STATUS
PeiUsbEnumeration (
  IN EFI_PEI_SERVICES               **PeiServices,
  IN PEI_USB_HOST_CONTROLLER_PPI    *UsbHcPpi,
  IN PEI_USB2_HOST_CONTROLLER_PPI   *Usb2HcPpi
  )
{
  UINT8                 NumOfRootPort;
  EFI_STATUS            Status;
  UINT8                 Index;
  EFI_USB_PORT_STATUS   PortStatus;
  PEI_USB_DEVICE        *PeiUsbDevice;
  UINTN                 MemPages;
  EFI_PHYSICAL_ADDRESS  AllocateAddress;
  UINT8                 CurrentAddress;
  UINTN                 InterfaceIndex;
  UINTN                 EndpointIndex;

  CurrentAddress = 0;
  if (Usb2HcPpi != NULL) {
    Usb2HcPpi->GetRootHubPortNumber (
                PeiServices,
                Usb2HcPpi,
                (UINT8 *) &NumOfRootPort
                );
  } else if (UsbHcPpi != NULL) {
    UsbHcPpi->GetRootHubPortNumber (
                PeiServices,
                UsbHcPpi,
                (UINT8 *) &NumOfRootPort
                );
  } else {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  DEBUG ((EFI_D_INFO, "PeiUsbEnumeration: NumOfRootPort: %x\n", NumOfRootPort));

  for (Index = 0; Index < NumOfRootPort; Index++) {
    //
    // First get root port status to detect changes happen
    //
    if (Usb2HcPpi != NULL) {
      Usb2HcPpi->GetRootHubPortStatus (
                  PeiServices,
                  Usb2HcPpi,
                  (UINT8) Index,
                  &PortStatus
                  );
    } else {
      UsbHcPpi->GetRootHubPortStatus (
                  PeiServices,
                  UsbHcPpi,
                  (UINT8) Index,
                  &PortStatus
                  );
    }
    DEBUG ((EFI_D_INFO, "USB Status --- Port: %x ConnectChange[%04x] Status[%04x]\n", Index, PortStatus.PortChangeStatus, PortStatus.PortStatus));
    //
    // Only handle connection/enable/overcurrent/reset change.
    //
    if ((PortStatus.PortChangeStatus & (USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE | USB_PORT_STAT_C_OVERCURRENT | USB_PORT_STAT_C_RESET)) == 0) {
      continue;
    } else {
      if (IsPortConnect (PortStatus.PortStatus)) {
        MemPages = sizeof (PEI_USB_DEVICE) / EFI_PAGE_SIZE + 1;
        Status = PeiServicesAllocatePages (
                   EfiBootServicesCode,
                   MemPages,
                   &AllocateAddress
                   );
        if (EFI_ERROR (Status)) {
          return EFI_OUT_OF_RESOURCES;
        }

        PeiUsbDevice = (PEI_USB_DEVICE *) ((UINTN) AllocateAddress);
        ZeroMem (PeiUsbDevice, sizeof (PEI_USB_DEVICE));

        PeiUsbDevice->Signature         = PEI_USB_DEVICE_SIGNATURE;
        PeiUsbDevice->DeviceAddress     = 0;
        PeiUsbDevice->MaxPacketSize0    = 8;
        PeiUsbDevice->DataToggle        = 0;
        CopyMem (
          &(PeiUsbDevice->UsbIoPpi),
          &mUsbIoPpi,
          sizeof (PEI_USB_IO_PPI)
          );
        CopyMem (
          &(PeiUsbDevice->UsbIoPpiList),
          &mUsbIoPpiList,
          sizeof (EFI_PEI_PPI_DESCRIPTOR)
          );
        PeiUsbDevice->UsbIoPpiList.Ppi  = &PeiUsbDevice->UsbIoPpi;
        PeiUsbDevice->AllocateAddress   = (UINTN) AllocateAddress;
        PeiUsbDevice->UsbHcPpi          = UsbHcPpi;
        PeiUsbDevice->Usb2HcPpi         = Usb2HcPpi;
        PeiUsbDevice->IsHub             = 0x0;
        PeiUsbDevice->DownStreamPortNo  = 0x0;

        if (((PortStatus.PortChangeStatus & USB_PORT_STAT_C_RESET) == 0) ||
             ((PortStatus.PortStatus & (USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE)) == 0)) {
          //
          // If the port already has reset change flag and is connected and enabled, skip the port reset logic.
          //
          ResetRootPort (
            PeiServices,
            PeiUsbDevice->UsbHcPpi,
            PeiUsbDevice->Usb2HcPpi,
            Index,
            0
            );

          if (Usb2HcPpi != NULL) {
            Usb2HcPpi->GetRootHubPortStatus (
                         PeiServices,
                         Usb2HcPpi,
                         (UINT8) Index,
                         &PortStatus
                         );
          } else {
            UsbHcPpi->GetRootHubPortStatus (
                        PeiServices,
                        UsbHcPpi,
                        (UINT8) Index,
                        &PortStatus
                        );
          }
        } else {
          if (Usb2HcPpi != NULL) {
            Usb2HcPpi->ClearRootHubPortFeature (
                        PeiServices,
                        Usb2HcPpi,
                        (UINT8) Index,
                        EfiUsbPortResetChange
                        );
          } else {
            UsbHcPpi->ClearRootHubPortFeature (
                        PeiServices,
                        UsbHcPpi,
                        (UINT8) Index,
                        EfiUsbPortResetChange
                        );
          }
        }

        PeiUsbDevice->DeviceSpeed = (UINT8) PeiUsbGetDeviceSpeed (PortStatus.PortStatus);
        DEBUG ((EFI_D_INFO, "Device Speed =%d\n", PeiUsbDevice->DeviceSpeed));

        if (USB_BIT_IS_SET (PortStatus.PortStatus, USB_PORT_STAT_SUPER_SPEED)){
          PeiUsbDevice->MaxPacketSize0 = 512;
        } else if (USB_BIT_IS_SET (PortStatus.PortStatus, USB_PORT_STAT_HIGH_SPEED)) {
          PeiUsbDevice->MaxPacketSize0 = 64;
        } else if (USB_BIT_IS_SET (PortStatus.PortStatus, USB_PORT_STAT_LOW_SPEED)) {
          PeiUsbDevice->MaxPacketSize0 = 8;
        } else {
          PeiUsbDevice->MaxPacketSize0 = 8;
        }

        //
        // Configure that Usb Device
        //
        Status = PeiConfigureUsbDevice (
                  PeiServices,
                  PeiUsbDevice,
                  Index,
                  &CurrentAddress
                  );

        if (EFI_ERROR (Status)) {
          continue;
        }
        DEBUG ((EFI_D_INFO, "PeiUsbEnumeration: PeiConfigureUsbDevice Success\n"));

        Status = PeiServicesInstallPpi (&PeiUsbDevice->UsbIoPpiList);

        if (PeiUsbDevice->InterfaceDesc->InterfaceClass == 0x09) {
          PeiUsbDevice->IsHub = 0x1;

          Status = PeiDoHubConfig (PeiServices, PeiUsbDevice);
          if (EFI_ERROR (Status)) {
            return Status;
          }

          PeiHubEnumeration (PeiServices, PeiUsbDevice, &CurrentAddress);
        }

        for (InterfaceIndex = 1; InterfaceIndex < PeiUsbDevice->ConfigDesc->NumInterfaces; InterfaceIndex++) {
          //
          // Begin to deal with the new device
          //
          MemPages = sizeof (PEI_USB_DEVICE) / EFI_PAGE_SIZE + 1;
          Status = PeiServicesAllocatePages (
                     EfiBootServicesCode,
                     MemPages,
                     &AllocateAddress
                     );
          if (EFI_ERROR (Status)) {
            return EFI_OUT_OF_RESOURCES;
          }
          CopyMem ((VOID *)(UINTN)AllocateAddress, PeiUsbDevice, sizeof (PEI_USB_DEVICE));
          PeiUsbDevice = (PEI_USB_DEVICE *) ((UINTN) AllocateAddress);
          PeiUsbDevice->AllocateAddress  = (UINTN) AllocateAddress;
          PeiUsbDevice->UsbIoPpiList.Ppi = &PeiUsbDevice->UsbIoPpi;
          PeiUsbDevice->InterfaceDesc = PeiUsbDevice->InterfaceDescList[InterfaceIndex];
          for (EndpointIndex = 0; EndpointIndex < PeiUsbDevice->InterfaceDesc->NumEndpoints; EndpointIndex++) {
            PeiUsbDevice->EndpointDesc[EndpointIndex] = PeiUsbDevice->EndpointDescList[InterfaceIndex][EndpointIndex];
          }

          Status = PeiServicesInstallPpi (&PeiUsbDevice->UsbIoPpiList);

          if (PeiUsbDevice->InterfaceDesc->InterfaceClass == 0x09) {
            PeiUsbDevice->IsHub = 0x1;

            Status = PeiDoHubConfig (PeiServices, PeiUsbDevice);
            if (EFI_ERROR (Status)) {
              return Status;
            }

            PeiHubEnumeration (PeiServices, PeiUsbDevice, &CurrentAddress);
          }
        }
      } else {
        //
        // Disconnect change happen, currently we don't support
        //
      }
    }
  }

  return EFI_SUCCESS;
}

/**
  Configure new detected usb device.

  @param  PeiServices            Describes the list of possible PEI Services.
  @param  PeiUsbDevice           The pointer of PEI_USB_DEVICE instance.
  @param  Port                   The port to be configured.
  @param  DeviceAddress          The device address to be configured.

  @retval EFI_SUCCESS            The new detected usb device is configured successfully.
  @retval EFI_OUT_OF_RESOURCES   Can't allocate memory resource.
  @retval Others                 Other failure occurs.

**/
EFI_STATUS
PeiConfigureUsbDevice (
  IN EFI_PEI_SERVICES   **PeiServices,
  IN PEI_USB_DEVICE     *PeiUsbDevice,
  IN UINT8              Port,
  IN OUT UINT8          *DeviceAddress
  )
{
  EFI_USB_DEVICE_DESCRIPTOR   DeviceDescriptor;
  EFI_STATUS                  Status;
  PEI_USB_IO_PPI              *UsbIoPpi;
  UINT8                       Retry;

  UsbIoPpi = &PeiUsbDevice->UsbIoPpi;
  Status   = EFI_SUCCESS;
  ZeroMem (&DeviceDescriptor, sizeof (EFI_USB_DEVICE_DESCRIPTOR));
  //
  // Get USB device descriptor
  //

  for (Retry = 0; Retry < 3; Retry ++) {
    Status = PeiUsbGetDescriptor (
               PeiServices,
               UsbIoPpi,
               (USB_DT_DEVICE << 8),
               0,
               8,
               &DeviceDescriptor
               );

    if (!EFI_ERROR (Status)) {
      DEBUG ((EFI_D_INFO, "PeiUsbGet Device Descriptor the %d time Success\n", Retry));
      break;
    }
  }

  if (Retry == 3) {
    DEBUG ((EFI_D_ERROR, "PeiUsbGet Device Descriptor fail: %x %r\n", Retry, Status));
    return Status;
  }

  if ((DeviceDescriptor.BcdUSB >= 0x0300) && (DeviceDescriptor.MaxPacketSize0 == 9)) {
    PeiUsbDevice->MaxPacketSize0 = 1 << 9;
  } else {
    PeiUsbDevice->MaxPacketSize0 = DeviceDescriptor.MaxPacketSize0;
  }

  (*DeviceAddress) ++;

  Status = PeiUsbSetDeviceAddress (
            PeiServices,
            UsbIoPpi,
            *DeviceAddress
            );

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "PeiUsbSetDeviceAddress Failed: %r\n", Status));
    return Status;
  }
  MicroSecondDelay (USB_SET_DEVICE_ADDRESS_STALL);

  PeiUsbDevice->DeviceAddress = *DeviceAddress;

  //
  // Get whole USB device descriptor
  //
  Status = PeiUsbGetDescriptor (
            PeiServices,
            UsbIoPpi,
            (USB_DT_DEVICE << 8),
            0,
            (UINT16) sizeof (EFI_USB_DEVICE_DESCRIPTOR),
            &DeviceDescriptor
            );

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "PeiUsbGetDescriptor First Failed\n"));
    return Status;
  }

  //
  // Get its default configuration and its first interface
  //
  Status = PeiUsbGetAllConfiguration (
            PeiServices,
            PeiUsbDevice
            );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  MicroSecondDelay (USB_GET_CONFIG_DESCRIPTOR_STALL);

  Status = PeiUsbSetConfiguration (
            PeiServices,
            UsbIoPpi
            );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  return EFI_SUCCESS;
}

/**
  Get all configurations from a detected usb device.

  @param  PeiServices            Describes the list of possible PEI Services.
  @param  PeiUsbDevice           The pointer of PEI_USB_DEVICE instance.

  @retval EFI_SUCCESS            The new detected usb device is configured successfully.
  @retval EFI_OUT_OF_RESOURCES   Can't allocate memory resource.
  @retval Others                 Other failure occurs.

**/
EFI_STATUS
PeiUsbGetAllConfiguration (
  IN EFI_PEI_SERVICES   **PeiServices,
  IN PEI_USB_DEVICE     *PeiUsbDevice
  )
{
  EFI_STATUS                Status;
  EFI_USB_CONFIG_DESCRIPTOR *ConfigDesc;
  PEI_USB_IO_PPI            *UsbIoPpi;
  UINT16                    ConfigDescLength;
  UINT8                     *Ptr;
  UINTN                     SkipBytes;
  UINTN                     LengthLeft;
  UINTN                     InterfaceIndex;
  UINTN                     Index;
  UINTN                     NumOfEndpoint;

  UsbIoPpi = &PeiUsbDevice->UsbIoPpi;

  //
  // First get its 4-byte configuration descriptor
  //
  Status = PeiUsbGetDescriptor (
            PeiServices,
            UsbIoPpi,
            (USB_DT_CONFIG << 8), // Value
            0,      // Index
            4,      // Length
            PeiUsbDevice->ConfigurationData
            );

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "PeiUsbGet Config Descriptor First Failed\n"));
    return Status;
  }
  MicroSecondDelay (USB_GET_CONFIG_DESCRIPTOR_STALL);

  ConfigDesc        = (EFI_USB_CONFIG_DESCRIPTOR *) PeiUsbDevice->ConfigurationData;
  ConfigDescLength  = ConfigDesc->TotalLength;

  //
  // Reject if TotalLength even cannot cover itself.
  //
  if (ConfigDescLength < OFFSET_OF (EFI_USB_CONFIG_DESCRIPTOR, TotalLength) + sizeof (ConfigDesc->TotalLength)) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Reject if TotalLength exceeds the PeiUsbDevice->ConfigurationData.
  //
  if (ConfigDescLength > sizeof (PeiUsbDevice->ConfigurationData)) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Then we get the total descriptors for this configuration
  //
  Status = PeiUsbGetDescriptor (
            PeiServices,
            UsbIoPpi,
            (USB_DT_CONFIG << 8),
            0,
            ConfigDescLength,
            PeiUsbDevice->ConfigurationData
            );

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "PeiUsbGet Config Descriptor all Failed\n"));
    return Status;
  }
  //
  // Parse this configuration descriptor
  // First get the current config descriptor;
  //
  Status = GetExpectedDescriptor (
            PeiUsbDevice->ConfigurationData,
            ConfigDescLength,
            USB_DT_CONFIG,
            (UINT8) sizeof (EFI_USB_CONFIG_DESCRIPTOR),
            &SkipBytes
            );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  Ptr                       = PeiUsbDevice->ConfigurationData + SkipBytes;
  PeiUsbDevice->ConfigDesc  = (EFI_USB_CONFIG_DESCRIPTOR *) Ptr;

  Ptr += sizeof (EFI_USB_CONFIG_DESCRIPTOR);
  LengthLeft = ConfigDescLength - SkipBytes - sizeof (EFI_USB_CONFIG_DESCRIPTOR);

  for (InterfaceIndex = 0; InterfaceIndex < PeiUsbDevice->ConfigDesc->NumInterfaces; InterfaceIndex++) {

    //
    // Get the interface descriptor
    //
    Status = GetExpectedDescriptor (
              Ptr,
              LengthLeft,
              USB_DT_INTERFACE,
              (UINT8) sizeof (EFI_USB_INTERFACE_DESCRIPTOR),
              &SkipBytes
              );

    if (EFI_ERROR (Status)) {
      return Status;
    }

    Ptr += SkipBytes;
    if (InterfaceIndex == 0) {
      PeiUsbDevice->InterfaceDesc = (EFI_USB_INTERFACE_DESCRIPTOR *) Ptr;
    }
    PeiUsbDevice->InterfaceDescList[InterfaceIndex] = (EFI_USB_INTERFACE_DESCRIPTOR *) Ptr;

    Ptr += sizeof (EFI_USB_INTERFACE_DESCRIPTOR);
    LengthLeft -= SkipBytes;
    LengthLeft -= sizeof (EFI_USB_INTERFACE_DESCRIPTOR);

    //
    // Parse all the endpoint descriptor within this interface
    //
    NumOfEndpoint = PeiUsbDevice->InterfaceDescList[InterfaceIndex]->NumEndpoints;
    ASSERT (NumOfEndpoint <= MAX_ENDPOINT);

    for (Index = 0; Index < NumOfEndpoint; Index++) {
      //
      // Get the endpoint descriptor
      //
      Status = GetExpectedDescriptor (
                Ptr,
                LengthLeft,
                USB_DT_ENDPOINT,
                (UINT8) sizeof (EFI_USB_ENDPOINT_DESCRIPTOR),
                &SkipBytes
                );

      if (EFI_ERROR (Status)) {
        return Status;
      }

      Ptr += SkipBytes;
      if (InterfaceIndex == 0) {
        PeiUsbDevice->EndpointDesc[Index] = (EFI_USB_ENDPOINT_DESCRIPTOR *) Ptr;
      }
      PeiUsbDevice->EndpointDescList[InterfaceIndex][Index] = (EFI_USB_ENDPOINT_DESCRIPTOR *) Ptr;

      Ptr += sizeof (EFI_USB_ENDPOINT_DESCRIPTOR);
      LengthLeft -= SkipBytes;
      LengthLeft -= sizeof (EFI_USB_ENDPOINT_DESCRIPTOR);
    }
  }

  return EFI_SUCCESS;
}

/**
  Get the start position of next wanted descriptor.

  @param  Buffer            Buffer containing data to parse.
  @param  Length            Buffer length.
  @param  DescType          Descriptor type.
  @param  DescLength        Descriptor length.
  @param  ParsedBytes       Bytes has been parsed.

  @retval EFI_SUCCESS       Get wanted descriptor successfully.
  @retval EFI_DEVICE_ERROR  Error occurred.

**/
EFI_STATUS
GetExpectedDescriptor (
  IN  UINT8       *Buffer,
  IN  UINTN       Length,
  IN  UINT8       DescType,
  IN  UINT8       DescLength,
  OUT UINTN       *ParsedBytes
  )
{
  USB_DESC_HEAD   *Head;
  UINTN           Offset;

  //
  // Total length is too small that cannot hold the single descriptor header plus data.
  //
  if (Length <= sizeof (USB_DESC_HEAD)) {
    DEBUG ((DEBUG_ERROR, "GetExpectedDescriptor: met mal-format descriptor, total length = %d!\n", Length));
    return EFI_DEVICE_ERROR;
  }

  //
  // All the descriptor has a common LTV (Length, Type, Value)
  // format. Skip the descriptor that isn't of this Type
  //
  Offset = 0;
  Head   = (USB_DESC_HEAD *)Buffer;
  while (Offset < Length - sizeof (USB_DESC_HEAD)) {
    //
    // Above condition make sure Head->Len and Head->Type are safe to access
    //
    Head = (USB_DESC_HEAD *)&Buffer[Offset];

    if (Head->Len == 0) {
      DEBUG ((DEBUG_ERROR, "GetExpectedDescriptor: met mal-format descriptor, Head->Len = 0!\n"));
      return EFI_DEVICE_ERROR;
    }

    //
    // Make sure no overflow when adding Head->Len to Offset.
    //
    if (Head->Len > MAX_UINTN - Offset) {
      DEBUG ((DEBUG_ERROR, "GetExpectedDescriptor: met mal-format descriptor, Head->Len = %d!\n", Head->Len));
      return EFI_DEVICE_ERROR;
    }

    if (Head->Type == DescType) {
      break;
    }

    Offset += Head->Len;
  }

  //
  // Head->Len is invalid resulting data beyond boundary, or
  // Descriptor cannot be found: No such type.
  //
  if (Length < Offset) {
    DEBUG ((DEBUG_ERROR, "GetExpectedDescriptor: met mal-format descriptor, Offset/Len = %d/%d!\n", Offset, Length));
    return EFI_DEVICE_ERROR;
  }

  if ((Head->Type != DescType) || (Head->Len < DescLength)) {
    DEBUG ((DEBUG_ERROR, "GetExpectedDescriptor: descriptor cannot be found, Header(T/L) = %d/%d!\n", Head->Type, Head->Len));
    return EFI_DEVICE_ERROR;
  }

  *ParsedBytes = Offset;
  return EFI_SUCCESS;
}

/**
  Send reset signal over the given root hub port.

  @param  PeiServices       Describes the list of possible PEI Services.
  @param  UsbHcPpi          The pointer of PEI_USB_HOST_CONTROLLER_PPI instance.
  @param  Usb2HcPpi         The pointer of PEI_USB2_HOST_CONTROLLER_PPI instance.
  @param  PortNum           The port to be reset.
  @param  RetryIndex        The retry times.

**/
VOID
ResetRootPort (
  IN EFI_PEI_SERVICES               **PeiServices,
  IN PEI_USB_HOST_CONTROLLER_PPI    *UsbHcPpi,
  IN PEI_USB2_HOST_CONTROLLER_PPI   *Usb2HcPpi,
  IN UINT8                          PortNum,
  IN UINT8                          RetryIndex
  )
{
  EFI_STATUS             Status;
  UINTN                  Index;
  EFI_USB_PORT_STATUS    PortStatus;


  if (Usb2HcPpi != NULL) {
    MicroSecondDelay (200 * 1000);

    //
    // reset root port
    //
    Status = Usb2HcPpi->SetRootHubPortFeature (
                         PeiServices,
                         Usb2HcPpi,
                         PortNum,
                         EfiUsbPortReset
                         );

    if (EFI_ERROR (Status)) {
      DEBUG ((EFI_D_ERROR, "SetRootHubPortFeature EfiUsbPortReset Failed\n"));
      return;
    }

    //
    // Drive the reset signal for at least 50ms. Check USB 2.0 Spec
    // section 7.1.7.5 for timing requirements.
    //
    MicroSecondDelay (USB_SET_ROOT_PORT_RESET_STALL);

    //
    // clear reset root port
    //
    Status = Usb2HcPpi->ClearRootHubPortFeature (
                         PeiServices,
                         Usb2HcPpi,
                         PortNum,
                         EfiUsbPortReset
                         );

    if (EFI_ERROR (Status)) {
      DEBUG ((EFI_D_ERROR, "ClearRootHubPortFeature EfiUsbPortReset Failed\n"));
      return;
    }

    MicroSecondDelay (USB_CLR_ROOT_PORT_RESET_STALL);

    //
    // USB host controller won't clear the RESET bit until
    // reset is actually finished.
    //
    ZeroMem (&PortStatus, sizeof (EFI_USB_PORT_STATUS));

    for (Index = 0; Index < USB_WAIT_PORT_STS_CHANGE_LOOP; Index++) {
      Status = Usb2HcPpi->GetRootHubPortStatus (
                            PeiServices,
                            Usb2HcPpi,
                            PortNum,
                            &PortStatus
                            );
      if (EFI_ERROR (Status)) {
        return;
      }

      if (!USB_BIT_IS_SET (PortStatus.PortStatus, USB_PORT_STAT_RESET)) {
        break;
      }

      MicroSecondDelay (USB_WAIT_PORT_STS_CHANGE_STALL);
    }

    if (Index == USB_WAIT_PORT_STS_CHANGE_LOOP) {
      DEBUG ((EFI_D_ERROR, "ResetRootPort: reset not finished in time on port %d\n", PortNum));
      return;
    }

    Usb2HcPpi->ClearRootHubPortFeature (
                PeiServices,
                Usb2HcPpi,
                PortNum,
                EfiUsbPortResetChange
                );

    Usb2HcPpi->ClearRootHubPortFeature (
                PeiServices,
                Usb2HcPpi,
                PortNum,
                EfiUsbPortConnectChange
                );

    //
    // Set port enable
    //
    Usb2HcPpi->SetRootHubPortFeature(
                PeiServices,
                Usb2HcPpi,
                PortNum,
                EfiUsbPortEnable
                );

    Usb2HcPpi->ClearRootHubPortFeature (
                PeiServices,
                Usb2HcPpi,
                PortNum,
                EfiUsbPortEnableChange
                );

    MicroSecondDelay ((RetryIndex + 1) * 50 * 1000);
  } else {
    MicroSecondDelay (200 * 1000);

    //
    // reset root port
    //
    Status = UsbHcPpi->SetRootHubPortFeature (
                         PeiServices,
                         UsbHcPpi,
                         PortNum,
                         EfiUsbPortReset
                         );

    if (EFI_ERROR (Status)) {
      DEBUG ((EFI_D_ERROR, "SetRootHubPortFeature EfiUsbPortReset Failed\n"));
      return;
    }

    //
    // Drive the reset signal for at least 50ms. Check USB 2.0 Spec
    // section 7.1.7.5 for timing requirements.
    //
    MicroSecondDelay (USB_SET_ROOT_PORT_RESET_STALL);

    //
    // clear reset root port
    //
    Status = UsbHcPpi->ClearRootHubPortFeature (
                         PeiServices,
                         UsbHcPpi,
                         PortNum,
                         EfiUsbPortReset
                         );

    if (EFI_ERROR (Status)) {
      DEBUG ((EFI_D_ERROR, "ClearRootHubPortFeature EfiUsbPortReset Failed\n"));
      return;
    }

    MicroSecondDelay (USB_CLR_ROOT_PORT_RESET_STALL);

    //
    // USB host controller won't clear the RESET bit until
    // reset is actually finished.
    //
    ZeroMem (&PortStatus, sizeof (EFI_USB_PORT_STATUS));

    for (Index = 0; Index < USB_WAIT_PORT_STS_CHANGE_LOOP; Index++) {
      Status = UsbHcPpi->GetRootHubPortStatus (
                           PeiServices,
                           UsbHcPpi,
                           PortNum,
                           &PortStatus
                           );
      if (EFI_ERROR (Status)) {
        return;
      }

      if (!USB_BIT_IS_SET (PortStatus.PortStatus, USB_PORT_STAT_RESET)) {
        break;
      }

      MicroSecondDelay (USB_WAIT_PORT_STS_CHANGE_STALL);
    }

    if (Index == USB_WAIT_PORT_STS_CHANGE_LOOP) {
      DEBUG ((EFI_D_ERROR, "ResetRootPort: reset not finished in time on port %d\n", PortNum));
      return;
    }

    UsbHcPpi->ClearRootHubPortFeature (
                PeiServices,
                UsbHcPpi,
                PortNum,
                EfiUsbPortResetChange
                );

    UsbHcPpi->ClearRootHubPortFeature (
                PeiServices,
                UsbHcPpi,
                PortNum,
                EfiUsbPortConnectChange
                );

    //
    // Set port enable
    //
    UsbHcPpi->SetRootHubPortFeature(
                PeiServices,
                UsbHcPpi,
                PortNum,
                EfiUsbPortEnable
                );

    UsbHcPpi->ClearRootHubPortFeature (
                PeiServices,
                UsbHcPpi,
                PortNum,
                EfiUsbPortEnableChange
                );

    MicroSecondDelay ((RetryIndex + 1) * 50 * 1000);
  }
  return;
}


